use super::*;

/// JavaScript array wrapper
#[derive(Clone, Copy)]
pub struct Array<'val> {
	pub(crate) env: &'val Env,
	pub(crate) inner: sys::napi_value,
}

impl<'val> Array<'val> {
	/// Creates a new empty array
	pub fn new<'env>(env: &'env Env) -> Result<'static, Array<'env>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_create_array(env.raw(), &mut ret) }.result("napi_create_array")?;
		Ok(Array { env, inner: ret })
	}

	/// Creates an array with specified length
	pub fn with_length<'env>(env: &'env Env, length: usize) -> Result<'static, Array<'env>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_create_array_with_length(env.raw(), length, &mut ret) }.result("napi_create_array_with_length")?;
		Ok(Array { env, inner: ret })
	}

	/// Creates array from iterator
	pub fn from_iter<'env, T, I>(env: &'env Env, iter: I) -> Result<'static, Array<'env>>
	where
		T: IntoNapi,
		I: IntoIterator<Item = T>,
	{
		let array = Array::new(env)?;
		for (i, item) in iter.into_iter().enumerate() {
			let value = item.into_napi(env)?;
			array.set(i, value)?;
		}
		Ok(array)
	}

	/// Converts to generic Value
	pub fn v(&'_ self) -> Value<'val> {
		Value::new(self.env, self.inner)
	}

	/// Gets array length
	pub fn length(&'_ self) -> Result<'static, usize> {
		let mut ret = 0;
		unsafe { sys::napi_get_array_length(self.env.raw(), self.inner, &mut ret) }.result("napi_get_array_length")?;
		Ok(ret as _)
	}

	/// Checks if array is empty
	pub fn is_empty(&'_ self) -> Result<'static, bool> {
		Ok(self.length()? == 0)
	}

	/// Sets an element at the specified index
	pub fn set_element(&'_ self, key: u32, value: Value<'_>) -> Result<'static, ()> {
		unsafe { sys::napi_set_element(self.env.raw(), self.inner, key, value.raw()) }.result("napi_set_element")
	}

	/// Checks if an element exists at the specified index
	pub fn has_element(&'_ self, key: u32) -> Result<'static, bool> {
		let mut ret = false;
		unsafe { sys::napi_has_element(self.env.raw(), self.inner, key, &mut ret) }.result("napi_has_element")?;
		Ok(ret)
	}

	/// Gets an element at the specified index
	pub fn get_element(&'_ self, key: u32) -> Result<'static, Value<'val>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_get_element(self.env.raw(), self.inner, key, &mut ret) }.result("napi_get_element")?;
		Ok(Value::new(self.env, ret))
	}

	/// Deletes an element at the specified index
	pub fn delete_element(&'_ self, key: u32) -> Result<'static, bool> {
		let mut ret = false;
		unsafe { sys::napi_delete_element(self.env.raw(), self.inner, key, &mut ret) }.result("napi_delete_element")?;
		Ok(ret)
	}

	/// Sets element at index
	pub fn set<T: IntoNapi>(&'_ self, index: usize, value: T) -> Result<'static, ()> {
		let napi_value = value.into_napi(self.env)?;
		self.set_element(index as u32, napi_value)
	}

	/// Gets element at index
	pub fn get<T: FromNapi<'val>>(&'_ self, index: usize) -> Result<'static, T> {
		self.get_element(index as u32)?.to()
	}

	/// Checks if index exists
	pub fn contains(&'_ self, index: usize) -> Result<'static, bool> {
		self.has_element(index as u32)
	}

	/// Removes element at index
	pub fn remove(&'_ self, index: usize) -> Result<'static, bool> {
		self.delete_element(index as u32)
	}

	/// Pushes element to end
	pub fn push<T: IntoNapi>(&'_ self, value: T) -> Result<'static, ()> {
		let len = self.length()?;
		self.set(len, value)
	}

	/// Clears all elements
	pub fn clear(&'_ self) -> Result<'static, ()> {
		let len = self.length()?;
		for i in 0..len {
			self.delete_element(i as u32)?;
		}
		Ok(())
	}

	/// Creates iterator
	pub fn iter(&'val self) -> Result<'static, ArrayIterator<'val>> {
		ArrayIterator::new(self)
	}

	/// Converts to Vec
	pub fn to_vec<T: FromNapi<'val>>(&'_ self) -> Result<'static, Vec<T>> {
		self.v().to()
	}

	/// Maps elements using function
	pub fn map<F, T, U>(&'_ self, f: F) -> Result<'static, Array<'val>>
	where
		F: Fn(T) -> U,
		T: FromNapi<'val>,
		U: IntoNapi,
	{
		let len = self.length()?;
		let new_array = Array::with_length(self.env, len)?;
		for i in 0..len {
			let value: T = self.get(i)?;
			let mapped_value = f(value);
			new_array.set(i, mapped_value)?;
		}
		Ok(new_array)
	}

	/// Filters elements using predicate
	pub fn filter<F, T>(&'_ self, f: F) -> Result<'static, Array<'val>>
	where
		F: Fn(&T) -> bool,
		T: FromNapi<'val> + IntoNapi,
	{
		let len = self.length()?;
		let filtered_array = Array::new(self.env)?;
		for i in 0..len {
			let value: T = self.get(i)?;
			if f(&value) {
				filtered_array.push(value)?;
			}
		}
		Ok(filtered_array)
	}

	/// Finds first matching element
	pub fn find<F, T>(&'_ self, f: F) -> Result<'static, Option<T>>
	where
		F: Fn(&T) -> bool,
		T: FromNapi<'val>,
	{
		let len = self.length()?;
		for i in 0..len {
			let value: T = self.get(i)?;
			if f(&value) {
				return Ok(Some(value));
			}
		}
		Ok(None)
	}

	/// Checks if any element matches
	pub fn some<F, T>(&'_ self, f: F) -> Result<'static, bool>
	where
		F: Fn(&T) -> bool,
		T: FromNapi<'val>,
	{
		let len = self.length()?;
		for i in 0..len {
			let value: T = self.get(i)?;
			if f(&value) {
				return Ok(true);
			}
		}
		Ok(false)
	}

	/// Checks if all elements match
	pub fn every<F, T>(&'_ self, f: F) -> Result<'static, bool>
	where
		F: Fn(&T) -> bool,
		T: FromNapi<'val>,
	{
		let len = self.length()?;
		for i in 0..len {
			let value: T = self.get(i)?;
			if !f(&value) {
				return Ok(false);
			}
		}
		Ok(true)
	}

	/// Reduces array to single value
	pub fn reduce<F, T, U>(&'_ self, initial: U, f: F) -> Result<'static, U>
	where
		F: Fn(U, T) -> U,
		T: FromNapi<'val>,
		U: IntoNapi + FromNapi<'val>,
	{
		let len = self.length()?;
		let mut accumulator = initial;
		for i in 0..len {
			let value: T = self.get(i)?;
			accumulator = f(accumulator, value);
		}
		Ok(accumulator)
	}

	/// Concatenates with another array
	pub fn concat(&'_ self, other: &Array<'val>) -> Result<'static, Array<'val>> {
		let len1 = self.length()?;
		let len2 = other.length()?;
		let new_array = Array::with_length(self.env, len1 + len2)?;

		for i in 0..len1 {
			let value = self.get_element(i as u32)?;
			new_array.set(i, value)?;
		}

		for i in 0..len2 {
			let value = other.get_element(i as u32)?;
			new_array.set(len1 + i, value)?;
		}

		Ok(new_array)
	}

	/// Creates slice of array
	pub fn slice(&'_ self, start: usize, end: Option<usize>) -> Result<'static, Array<'val>> {
		let len = self.length()?;
		let end = end.unwrap_or(len);
		let start = start.min(len);
		let end = end.min(len);

		if start >= end {
			return Ok(Array::new(self.env)?);
		}

		let slice_len = end - start;
		let new_array = Array::with_length(self.env, slice_len)?;

		for i in 0..slice_len {
			let value = self.get_element((start + i) as u32)?;
			new_array.set(i, value)?;
		}

		Ok(new_array)
	}

	/// Reverses array elements
	pub fn reverse(&'_ self) -> Result<'static, Array<'val>> {
		let len = self.length()?;
		let new_array = Array::with_length(self.env, len)?;

		for i in 0..len {
			let value = self.get_element((len - 1 - i) as u32)?;
			new_array.set(i, value)?;
		}

		Ok(new_array)
	}
}

/// Array iterator
pub struct ArrayIterator<'val> {
	array: &'val Array<'val>,
	index: usize,
	length: usize,
}

impl<'val> ArrayIterator<'val> {
	/// Creates new iterator
	pub fn new(array: &'val Array<'val>) -> Result<'static, Self> {
		let length = array.length()?;
		Ok(Self { array, index: 0, length })
	}
}

impl<'val> Iterator for ArrayIterator<'val> {
	type Item = Result<'static, Value<'val>>;

	/// Gets next element
	fn next(&mut self) -> Option<Self::Item> {
		if self.index >= self.length {
			return None;
		}

		let result = self.array.get_element(self.index as u32);
		self.index += 1;
		Some(result)
	}
}

impl<'val> FromNapi<'val> for Array<'val> {
	/// Converts from Value to Array
	fn from_napi(v: Value<'val>) -> Result<'static, Self> {
		if v.is_array()? {
			Ok(Array {
				env: v.env,
				inner: v.inner,
			})
		} else {
			Err(Status::invalid_arg("value is not Array type"))
		}
	}
}

impl<'val> IntoNapi for Array<'val> {
	/// Converts Array to Value
	fn into_napi<'env>(self, env: &'env Env) -> Result<'static, Value<'env>> {
		if self.env.raw() != env.raw() {
			return Err(Status::invalid_arg("self.env not eq arg env"));
		}
		Ok(Value::new(env, self.inner))
	}
}

impl<T: IntoNapi> IntoNapi for Vec<T> {
	/// Converts Vec to JavaScript array
	fn into_napi<'env>(self, env: &'env Env) -> Result<'static, Value<'env>> {
		let array = Array::with_length(env, self.len())?;
		for (i, item) in self.into_iter().enumerate() {
			let value = item.into_napi(env)?;
			array.set(i, value)?;
		}
		Ok(array.into_napi(env)?)
	}
}

impl<'c, T: IntoNapi + Clone> IntoNapi for &'c [T] {
	/// Converts slice to JavaScript array
	fn into_napi<'env>(self, env: &'env Env) -> Result<'static, Value<'env>> {
		let array = Array::with_length(env, self.len())?;
		for (i, item) in self.iter().enumerate() {
			let value = item.clone().into_napi(env)?;
			array.set(i, value)?;
		}
		Ok(array.into_napi(env)?)
	}
}

impl<T: IntoNapi, const N: usize> IntoNapi for [T; N] {
	/// Converts array to JavaScript array
	fn into_napi<'env>(self, env: &'env Env) -> Result<'static, Value<'env>> {
		let vec: Vec<T> = self.into();
		vec.into_napi(env)
	}
}

impl<'env, T: FromNapi<'env>> FromNapi<'env> for Vec<T> {
	/// Converts JavaScript array to Vec
	fn from_napi(v: Value<'env>) -> Result<'static, Self> {
		let v = v.to::<Array>()?;
		let len = v.length()?;
		let mut result = Vec::with_capacity(len);
		for i in 0..len {
			let element = v.get_element(i as _)?;
			result.push(element.to()?);
		}
		Ok(result)
	}
}
